using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.Repository.Configuration;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Practices.Repository
{
    /// <summary>
    /// Factory class that creates an instance of a repository 
    /// for a given interface. The mapping between interfaces and 
    /// concrete types is done on the configuration file.
    /// </summary>
    public static class RepositoryFactory
    {
        /// <summary>
        /// Creates an instance of a repository with the given interface.
        /// </summary>
        /// <typeparam name="TInterface">Interface type of the repository.</typeparam>
        /// <param name="connectionStringName">Connection string name used to initiliaze the repository.</param>
        /// <returns>An instance of a repository implementing the given interface.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static TInterface Create<TInterface>(string connectionStringName)
        {
            return (TInterface)Create(typeof(TInterface), connectionStringName);
        }

        /// <summary>
        /// Creates an instance of a repository with the given interface type.
        /// </summary>
        /// <param name="repositoryInterface">Interface type of the repository.</param>
        /// <param name="connectionStringName">Connection string name used to initialize the repository.</param>
        /// <returns>An instance of a repository implementing the given interface type.</returns>
        public static object Create(Type repositoryInterface, string connectionStringName)
        {
            if (repositoryInterface == null)
                throw new ArgumentNullException("repositoryInterface");

            if(string.IsNullOrEmpty(connectionStringName))
                throw new ArgumentNullException("connectionStringName");

            Type repositoryType = GetRepositoryType(repositoryInterface);

            return Activator.CreateInstance(repositoryType, connectionStringName);
        }

        /// <summary>
        /// Creates an instance of a repository with the given interface type.
        /// </summary>
        /// <param name="repositoryInterface">Interface type of the repository.</param>
        /// /// <returns>An instance of a repository implementing the given interface type.</returns>
        public static object Create(Type repositoryInterface)
        {
            if (repositoryInterface == null)
                throw new ArgumentNullException("repositoryInterface");

            Type repositoryType = GetRepositoryType(repositoryInterface);

            return Activator.CreateInstance(repositoryType);
        }

        /// <summary>
        /// Creates an instance of a repository with the given interface type.
        /// </summary>
        /// <typeparam name="TInterface">Interface type of the repository.</typeparam>
        /// <returns>An instance of a repository implementing the given interface type.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static TInterface Create<TInterface>()
        {
            return (TInterface)Create(typeof(TInterface));
        }

        private static Type GetRepositoryType(Type repositoryInterface)
        {
            RepositoryFactorySection section = (RepositoryFactorySection)ConfigurationManager.GetSection("repositoryFactory");

            if (section == null)
                throw new ConfigurationErrorsException();

            RepositoryElement element = section.Repositories[repositoryInterface];

            if (element == null)
                throw new ConfigurationErrorsException();

            Type repositoryType = Type.GetType(element.RepositoryType);

            if (repositoryType.GetInterface(repositoryInterface.Name) == null)
                throw new InvalidOperationException();

            return repositoryType;
        }
    }
}
